package jk.framework.spring.security.authorization;

import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.FilterInvocation;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;

import static jk.framework.spring.security.authorization.SecurityResource.*;
import static jk.framework.spring.security.BaseAccess.*;
/**
 * 动态投票
 *
 * @author cuichao
 */
public class DynamicDecisionVoter implements AccessDecisionVoter<FilterInvocation> {

    /**
     * Authentication 内置检查器
     */
    private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();


    @Override
    public boolean supports(ConfigAttribute attribute) {
        return attribute instanceof DynamicSecurityAttribute;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }

    @Override
    public int vote(Authentication authentication, FilterInvocation object, Collection<ConfigAttribute> attributes) {

        //非法操作
        if (authentication == null) {
            return ACCESS_DENIED;
        }

        //得到用户的权限
        Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);

        //如果系统内置权限为基础访问权限
        for (ConfigAttribute attribute : attributes) {
            if (hasBaseAccess(attribute.getAttribute())) {
                return applyBaseAccessStrategy(attribute.getAttribute(), authentication);
            }
        }


        return applyStrategyToVote(attributes, authorities);
    }

    /**
     * 应用基础权限策略
     *
     * @return
     */
    private int applyBaseAccessStrategy(String attribute, Authentication authentication) {
        switch (attribute) {
            case denyAll:
                return ACCESS_DENIED;
            case permitAll:
                return ACCESS_GRANTED;
            case authenticated:
                return isAuthenticated(authentication) ? ACCESS_GRANTED : ACCESS_DENIED;
            case fullyAuthenticated:
                return isFullyAuthenticated(authentication) ? ACCESS_GRANTED : ACCESS_DENIED;
            default:
                return ACCESS_ABSTAIN;
        }
    }


    /**
     * 查找策略并得出投票结果
     *
     * @param securityConfigs
     * @param authorities
     * @return
     */
    private int applyStrategyToVote(Collection<ConfigAttribute> securityConfigs, Collection<? extends GrantedAuthority> authorities) {
        String strategy = null;
        Boolean allowIfEqualGrantedDeniedDecisions = null;
        //check securityConfig
        for (ConfigAttribute securityConfig : securityConfigs) {
            if (supports(securityConfig)) {
                DynamicSecurityAttribute security = (DynamicSecurityAttribute) securityConfig;
                if (StringUtils.isEmpty(strategy)) {
                    strategy = security.getStrategy();
                }
                if (allowIfEqualGrantedDeniedDecisions == null) {
                    allowIfEqualGrantedDeniedDecisions = security.isAllowIfEqualGrantedDeniedDecisions();
                }

            }
        }


        if (StringUtils.isEmpty(strategy)) {
            return ACCESS_ABSTAIN;
        }
        //choose strategy
        switch (strategy) {
            case AFFIRMATIVE_STRATEGY:
                return affirmativeVote(securityConfigs, authorities);
            case CONSENSUS_STRATEGY:
                return consensusVote(securityConfigs, authorities);
            case UNANIMOUS_STRATEGY:
                return unanimousVote(securityConfigs, authorities, allowIfEqualGrantedDeniedDecisions);
            default:
                return ACCESS_GRANTED;
        }

    }

    /**
     * 肯定式投票
     *
     * @param configs
     * @param authorities
     * @return
     */
    private int affirmativeVote(Collection<ConfigAttribute> configs, Collection<? extends GrantedAuthority> authorities) {

        int result = ACCESS_ABSTAIN;

        for (ConfigAttribute config : configs) {
            result = ACCESS_DENIED;
            for (GrantedAuthority authority : authorities) {
                if (config.getAttribute().equals(authority.getAuthority())) {
                    return ACCESS_GRANTED;
                }
            }
        }
        return result;
    }

    /**
     * 一致式投票
     *
     * @param configs
     * @param authorities
     * @return
     */
    private int consensusVote(Collection<ConfigAttribute> configs, Collection<? extends GrantedAuthority> authorities) {
        int result = ACCESS_ABSTAIN;

        for (ConfigAttribute config : configs) {
            result = ACCESS_GRANTED;
            for (GrantedAuthority authority : authorities) {
                if (!config.getAttribute().equals(authority.getAuthority())) {
                    return ACCESS_DENIED;
                }
            }
        }

        return result;
    }

    /**
     * 多数式投票
     *
     * @param configs
     * @param authorities
     * @return
     */
    private int unanimousVote(Collection<ConfigAttribute> configs, Collection<? extends GrantedAuthority> authorities, boolean allowIfEqualGrantedDeniedDecisions) {

        int totalVote = configs.size();

        if (totalVote == 0) {
            return ACCESS_ABSTAIN;
        }

        Set<String> collect = authorities.parallelStream().filter(v -> StringUtils.isEmpty(((GrantedAuthority) v)
                .getAuthority().trim())).map(v -> ((GrantedAuthority) v).getAuthority()).collect(Collectors.toSet());

        if (CollectionUtils.isEmpty(collect)) {
            return ACCESS_DENIED;
        }

        //vote
        int grantVote = 0;
        int deniedVote = 0;

        for (ConfigAttribute config : configs) {
            if (collect.contains(config.getAttribute())) {
                ++grantVote;
            } else {
                ++deniedVote;
            }
        }

        if (grantVote > deniedVote) {
            return ACCESS_GRANTED;
        } else if (grantVote < deniedVote) {
            return ACCESS_DENIED;
        } else if (allowIfEqualGrantedDeniedDecisions) {
            return ACCESS_GRANTED;
        } else {
            return ACCESS_DENIED;
        }

    }

    /**
     * 得到用户的权限别表
     *
     * @param authentication
     * @return
     */
    Collection<? extends GrantedAuthority> extractAuthorities(Authentication authentication) {
        return authentication.getAuthorities();
    }


    /**
     * 判断是否是匿名用户
     *
     * @param authentication
     * @return
     */
    public final boolean isAnonymous(Authentication authentication) {
        return trustResolver.isAnonymous(authentication);
    }

    /**
     * 是否是认证过的
     *
     * @return
     */
    public final boolean isAuthenticated(Authentication authentication) {
        return !isAnonymous(authentication);
    }

    /**
     * 是否通过RememberMe功能通过认证的
     *
     * @param authentication
     * @return
     */
    public final boolean isRememberMe(Authentication authentication) {
        return trustResolver.isRememberMe(authentication);
    }

    /**
     * 是否是通过正常认证手段通过的(非记住我/匿名登录)
     *
     * @param authentication
     * @return
     */
    public final boolean isFullyAuthenticated(Authentication authentication) {
        return !trustResolver.isAnonymous(authentication) && !trustResolver.isRememberMe(authentication);
    }
}
